/**
 *  @author Morozan Ion
 *  
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.dddforandroid.crud;

import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import com.dddforandroid.classes.NotePad.Notes;
import com.dddforandroid.classes.NotesPad;
import com.dddforandroid.util.Util;
import com.google.appengine.api.NamespaceManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

@Path("/v1")
public class RESTfulservice {

	/**
	 * in browser: http://localhost:8888/api/v1/note/123
	 * 
	 * @param id
	 *            - the id of desire note
	 * @return - desire note
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/note/{id}")
	public Response getNoteById(@PathParam("id") String id) {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/* retriving the instance of the database */
		PersistenceManager db = PMF.get().getPersistenceManager();
		/* keep database consistent */
		Transaction transaction = db.currentTransaction();
		try {
			try {
				transaction.begin();
				/* get desired note */
				builder.entity(db.getObjectById(Notes.class, id));
				transaction.commit();
			} catch (Exception ex) {
				/* if smth went wrong */
				if (transaction.isActive()) {
					transaction.rollback();
					builder = Response.status(Response.Status.NOT_FOUND)
							.entity("Note not found! Check again!");
				}
			}
		} finally {
			db.close();
		}

		return builder.build();
	}

	/**
	 * in browser: http://localhost:8888/api/v1/note
	 * 
	 * @return JSON File with User's database
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/note")
	public Response getAllNotes() {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		/* Represents a response entity of a generic type T. */
		GenericEntity<List<Notes>> entity = null;

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/* retriving the instance of the database */
		PersistenceManager db = PMF.get().getPersistenceManager();

		try {
			/* making a list with all the entites from DB */
			javax.jdo.Query query = db.newQuery(Notes.class);
			@SuppressWarnings("unchecked")
			List<Notes> AllNotes = (List<Notes>) query.execute();
			/* if there is no data in DB -> Error message */
			if (!AllNotes.isEmpty()) {
				entity = new GenericEntity<List<Notes>>(AllNotes) {
				};
				builder.entity(entity);
			} else {
				builder = Response.status(Response.Status.NOT_FOUND).entity(
						"No notes available!");
			}
		} finally {
			db.close();
		}

		return builder.build();
	}

	/**
	 * curl example: curl -v http://localhost:8888/api/v1/note/update/123 -X PUT
	 * -d @test.json -H "Content-Type: application/json"
	 * 
	 * @param note
	 *            - one entry from user's database
	 * @param id
	 *            - the uuid that has to match with the one from the note
	 * 
	 */
	@PUT
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/note/update/{id}")
	public Response UpdateAddNote(String note, @PathParam("id") String id) {

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/* getting the instance of the database */
		PersistenceManager db = PMF.get().getPersistenceManager();

		ResponseBuilder builder = null;

		GsonBuilder GsonBuilder = new GsonBuilder();
		/** Register an adapter to manage the date types as long values */
		GsonBuilder.registerTypeAdapter(Date.class,
				new JsonDeserializer<Date>() {
					@Override
					public Date deserialize(JsonElement json, Type arg1,
							JsonDeserializationContext arg2)
							throws JsonParseException {
						return new Date(json.getAsJsonPrimitive().getAsLong());
					}
				});

		/* new Gson element */
		Gson gson = GsonBuilder.create();
		/* deserializing from Json to <Note> */
		Notes newNote = gson.fromJson(note, Notes.class);

		/* make sure that the ids matches */
		if (newNote.getId().equals(id)) {
			/* keep database consistent */
			Transaction transaction = db.currentTransaction();
			try {
				/** add information about current user */
//				newNote.setEmailAddress(user.getUserEmail());
//				newNote.setUserId(user.getUserId());

				transaction.begin();
				/* Add each note to DB */
				db.makePersistent(newNote);
				transaction.commit();
			} catch (Exception ex) {
				/* if smth went wrong */
				if (transaction.isActive()) {
					transaction.rollback();

					builder = Response.status(Response.Status.BAD_REQUEST)
							.entity(HandlingMessage("Error", "error",
									"There was an error with the note with this email:"
											+ user.getUserEmail()));
					/* close the DB in a consistent state */
					db.close();
					/* Send response */
					return builder.build();
				}
			}
			/* close the DB in a consistent state */
			db.close();

			/* set the status that the content was added / updated to DB */
			builder = Response.status(Response.Status.CREATED).entity(
					HandlingMessage("Success", "success",
							"Your note was successfully updated / created!"));
		} else {
			builder = Response
					.status(Response.Status.BAD_REQUEST)
					.entity(HandlingMessage("Error", "error",
							"Wrong Data Format or Different IDs(Req + Content)! Accept only JSON!"));
		}
		return builder.build();
	}

	/**
	 * curl example: curl -v http://localhost:8888/api/v1/note/create -X POST -d
	 * 
	 * @test.json -H "Content-Type: application/json"
	 * 
	 * @param Table
	 *            - User database
	 * @return Message to inform the client if transaction went well or not
	 */
	@POST
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/note/create")
	public Response AddNote(String Table) {

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/* getting the instance of the database */
		PersistenceManager db = PMF.get().getPersistenceManager();
		ResponseBuilder builder = null;

		GsonBuilder GsonBuilder = new GsonBuilder();
		/** Register an adapter to manage the date types as long values */
		GsonBuilder.registerTypeAdapter(Date.class,
				new JsonDeserializer<Date>() {
					@Override
					public Date deserialize(JsonElement json, Type arg1,
							JsonDeserializationContext arg2)
							throws JsonParseException {
						return new Date(json.getAsJsonPrimitive().getAsLong());
					}
				});

		/* new Gson element */
		Gson gson = GsonBuilder.create();
		/* deserializing from Json to <Note> */
		NotesPad notes = gson.fromJson(Table, NotesPad.class);

		/* check if the format of data is correct (JSON format) */
		if (isNotesContentCorrect(notes.getNotes())) {
			/* keep database consistent */
			Transaction transaction = db.currentTransaction();
			for (Notes note : notes.getNotes()) {
				try {
					/** add information about current user */
//					note.setEmailAddress(user.getUserEmail());
//					note.setUserId(user.getUserId());

					transaction.begin();
					/* Add each note to DB */
					db.makePersistent(note);
					transaction.commit();
				} catch (Exception ex) {
					/* if smth went wrong */
					if (transaction.isActive()) {
						transaction.rollback();
						builder = Response.status(Response.Status.BAD_REQUEST)
								.entity(HandlingMessage("Error", "error",
										"There was an error with the note with this email:"
												+ user.getUserEmail()));
						/* close the DB in a consistent state */
						db.close();
						/* Send response */
						return builder.build();
					}
				}
			}
		} else {
			builder = Response.status(Response.Status.BAD_REQUEST).entity(
					HandlingMessage("Error", "error",
							"Wrong Data Format! Accept only JSON!"));
			return builder.build();
		}
		/* close the DB in a consistent state */
		db.close();
		builder = Response.status(Response.Status.CREATED).entity(
				HandlingMessage("Success", "success",
						"Your Database was successfully synchronized!"));

		return builder.build();
	}

	/**
	 * curl example: curl -v http://localhost:8888/api/v1/note/delete/ -X DELETE
	 * -d @test.json -H "Content-Type: application/json"
	 * 
	 * @param Table
	 *            - User database
	 * @return Message to inform the client if transaction went well or not
	 */
	@DELETE
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/note/delete/{id}")
	public Response removeNoteWithId(@PathParam("id") String id) {
		ResponseBuilder builder = null;

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/* getting the instance of the database */
		PersistenceManager db = PMF.get().getPersistenceManager();

		/* keep database consistent */
		Transaction transaction = db.currentTransaction();
		/* for the given ID */
		try {
			transaction.begin();
			/* Delete each note from DB */
			db.deletePersistent(db.getObjectById(Notes.class, id));
			transaction.commit();
		} catch (Exception ex) {
			/* if smth went wrong */
			if (transaction.isActive()) {
				transaction.rollback();
				builder = Response.status(Response.Status.BAD_REQUEST).entity(
						HandlingMessage("Error", "error",
								"There is no note with this given ID: " + id));
				/* close the DB in a consistent state */
				db.close();
				/* Send response */
				return builder.build();
			}
		}

		/* close the DB in a consistent state */
		db.close();
		/* Set the response for success */
		builder = Response.status(Response.Status.NO_CONTENT).entity(
				HandlingMessage("Success", "success",
						"Notes successfully deleted"));

		/* Send response */
		return builder.build();
	}

	/**
	 * 
	 * @param MessageType
	 *            Error / Success
	 * @param MessageTag
	 *            error / success = type of the xml tag
	 * @param Message
	 *            The message that the client will see
	 * 
	 */
	private Document HandlingMessage(String MessageType, String MessageTag,
			String Message) {

		/* Create the Xml file */
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder = null;
		try {
			docBuilder = documentBuilderFactory.newDocumentBuilder();
		} catch (ParserConfigurationException ex) {
			Logger.getLogger(ex.getMessage());
		}
		Document doc = docBuilder.newDocument();

		Element RootElement = doc.createElement(MessageType);
		Element Node = doc.createElement(MessageTag);
		Text textNode = doc.createTextNode(Message);
		Node.appendChild(textNode);

		RootElement.appendChild(Node);
		doc.appendChild(RootElement);


		return doc;
	}

	/**
	 * Verify if all fields are present(is respected the class definition)
	 * 
	 * @param Notes
	 *            - List of notes
	 * @return if the content of list is correct
	 */
	private boolean isNotesContentCorrect(List<Notes> NotesP) {
		for (Notes note : NotesP) {
			if (note.getCreatedDate() == null || note.getId() == null
					|| note.getTitle()== null || note.getNote() == null) {
				return false;
			}
		}
		return true;
	}
}